package com.google.gwt.chart.client;

import com.google.gwt.user.client.ui.*;
import com.google.gwt.dom.client.*;
import com.google.gwt.widgetideas.graphics.client.*;
import java.util.List;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import java.util.ArrayList;

//=========================================================================
/**
 * Title:        Plot <br>
 * Description:  $defaultDesc$<br>
 * Copyright:    Apache Public License 2.0<br>
 * Company:      $company$<br>
 * @author James Hutton
 */
//=========================================================================
public class Plot
		extends Widget
		implements ClickListener, MouseListener {

	//=========================================================================
	/**
	 * Method getMagnitude
	 * @param x
	 * @return double
	 */
	//=========================================================================
	private static double getMagnitude(double x) {
		return Math.pow(10, Math.floor(Math.log10(x)));
	}

	/**
	 * Function calculates the ticksize and returns it;
	 * @param noTicks Number of ticks
	 * @param min Lower bound for the current axis
	 * @param max Upper bound for the current axis
	 * @param decimals Number of decimals for the ticks
	 * @return Returns the size of a tick
	 */
	private static double getTickSize(int noTicks, Number min, Number max, Number decimals) {

		double delta = (max.doubleValue() - min.doubleValue()) / noTicks;
		double magn = getMagnitude(delta);
		double norm = delta / magn;
		double tickSize = 10;
		if (norm < 1.5) {
			tickSize = 1;
		}
		else if (norm < 2.25) {
			tickSize = 2;
		}
		else if (norm < 3) {
			tickSize = 2.5;
		}
		else if (norm < 7.5) {
			tickSize = 5;
		}
		if ((tickSize == 2.5) && ((decimals == null) || (decimals.intValue() == 0))) {
			tickSize = 2;
		}
		tickSize *= magn;
		return tickSize;

	}

	private Canvas canvas, overlay;

	private int canvasHeight = 0;

	private int canvasWidth = 0;

	private Element div;
	private Command drawNow = new Command() {

		public void execute() {
			drawNow();
			insertLegend();
		}

	};
	private double hozScale = 0;
	private String id = "glotr-" + Glotr.plotCnt++;
	private boolean ignoreClick = false;
	private int labelMaxHeight = 0;
	private int labelMaxWidth = 0;
	//private Coordinate lastMousePos = new Coordinate(0, 0);
	private PlotOptions options;
	private int plotHeight = 0;
	private Bounds plotOffset = new Bounds(0, 0, 0, 0);
	private int plotWidth = 0;
	//private Coordinate[] selection = new Coordinate[]{ new Coordinate(-1, -1), new Coordinate(-1, -1) };
	private ArrayList<GlotrData> series = new ArrayList<GlotrData>();
	private double vertScale = 0;
	private Axis xaxis = new Axis();

	private ArrayList<DivElement> xLabels = new ArrayList<DivElement>();

	private Axis yaxis = new Axis();
	private ArrayList<DivElement> yLabels = new ArrayList<DivElement>();

	//=========================================================================
	/**
	 * Constructs a Plot
	 */
	//=========================================================================
	public Plot() {
		this(new PlotOptions(), 300, 300);
	}

	//=========================================================================
	/**
	 * Constructs a Plot
	 * @param width -
	 * @param height -
	 */
	//=========================================================================
	public Plot(int width, int height) {
		this(new PlotOptions(), width, height);
	}

	//=========================================================================
	/**
	 * Constructs a Plot
	 * @param options -
	 * @param width -
	 * @param height -
	 */
	//=========================================================================
	public Plot(PlotOptions options, int width, int height) {

		div = Document.get().createDivElement();
		setElement(div);
		div.setId(id);
		canvasWidth = width;
		canvasHeight = height;
		canvas = new Canvas(canvasWidth, canvasHeight);
		div.appendChild(canvas.getElement());
		overlay = new Canvas(canvasWidth, canvasHeight);
		overlay.addClickListener(this);
		overlay.addMouseListener(this);
		this.options = options;

	}

	//=========================================================================
	/**
	 * Method addSeries
	 * @param data
	 */
	//=========================================================================
	public void addSeries(GlotrData data) {
		series.add(data);
	}

	//=========================================================================
	/**
	 * Method clearSeries
	 */
	//=========================================================================
	public void clearSeries() {
		series.clear();
		canvas.clear();
	}

	//=========================================================================
	/**
	 * Method draw
	 */
	//=========================================================================
	public void draw() {

		findDataRanges();
		calculateRange(xaxis, options.xaxis);
		extendXRangeIfNeededByBar();
		calculateRange(yaxis, options.yaxis);
		calculateTicks(xaxis, options.xaxis);
		calculateTicks(yaxis, options.yaxis);
		calculateSpacing();
		DeferredCommand.addCommand(drawNow);

	}

	//=========================================================================
	/**
	 * Method getCanvas
	 * @return Canvas
	 */
	//=========================================================================
	public Canvas getCanvas() {
		return canvas;
	}

	//=========================================================================
	/**
	 * Method getOptions
	 * @return PlotOptions
	 */
	//=========================================================================
	public PlotOptions getOptions() {
		return options;
	}

	//=========================================================================
	/**
	 * Method getPlotOffset
	 * @return Bounds
	 */
	//=========================================================================
	public Bounds getPlotOffset() {
		return plotOffset;
	}

	//=========================================================================
	/**
	 * Method getSeries
	 * @param i
	 * @return GlotrData
	 */
	//=========================================================================
	public GlotrData getSeries(int i) {
		return series.get(i);
	}

	//=========================================================================
	/**
	 * Method onClick
	 * @param w
	 */
	//=========================================================================
	public void onClick(Widget w) {

		if (ignoreClick) {
			ignoreClick = false;
			return;
		}


	}

	//=========================================================================
	/**
	 * Method onMouseDown
	 * @param w
	 * @param x
	 * @param y
	 */
	//=========================================================================
	public void onMouseDown(Widget w, int x, int y) {}

	//=========================================================================
	/**
	 * Method onMouseEnter
	 * @param w
	 */
	//=========================================================================
	public void onMouseEnter(Widget w) {}

	//=========================================================================
	/**
	 * Method onMouseLeave
	 * @param w
	 */
	//=========================================================================
	public void onMouseLeave(Widget w) {}

	//=========================================================================
	/**
	 * Method onMouseMove
	 * @param w
	 * @param x
	 * @param y
	 */
	//=========================================================================
	public void onMouseMove(Widget w, int x, int y) {}

	//=========================================================================
	/**
	 * Method onMouseUp
	 * @param w
	 * @param x
	 * @param y
	 */
	//=========================================================================
	public void onMouseUp(Widget w, int x, int y) {}

	//=========================================================================
	/**
	 * Method removeSeries
	 * @param data
	 */
	//=========================================================================
	public void removeSeries(GlotrData data) {
		series.remove(data);
	}

	//=========================================================================
	/**
	 * Method setPixelSize
	 * @param width
	 * @param height
	 */
	//=========================================================================
	public void setPixelSize(int width, int height) {

		canvas.resize(width, height);
		canvasHeight = height;
		canvasWidth = width;

	}

	//=========================================================================
	/**
	 * Method setPlotOptions
	 * @param po
	 */
	//=========================================================================
	public void setPlotOptions(PlotOptions po) {
		options = po;
	}

	//=========================================================================
	/**
	 * Method calculateRange
	 * @param axis
	 * @param axisOptions
	 */
	//=========================================================================
	private void calculateRange(Axis axis, PlotOptions.AxisOptions axisOptions) {

		Number min = (axisOptions.min != null) ? axisOptions.min : axis.datamin;
		Number max = (axisOptions.max != null) ? axisOptions.max : axis.datamax;

		if (max.doubleValue() - min.doubleValue() == 0.0) {
			double widen = (max.doubleValue() == 0.0) ? 1.0 : 0.01;
			min = min.doubleValue() - widen;
			max = max.doubleValue() + widen;
		}
		axis.tickSize = getTickSize(axisOptions.noTicks, min, max, axisOptions.tickDecimals);

		double margin;
		if (axisOptions.min == null) {
			margin = axisOptions.autoscaleMargin;
			if (margin != 0) {
				min = min.doubleValue() - axis.tickSize * margin;
				if ((min.doubleValue() < 0) && (axis.datamin.doubleValue() >= 0)) {
					min = 0;
				}
				min = axis.tickSize * Math.floor(min.doubleValue() / axis.tickSize);
			}
		}
		if (axisOptions.max == null) {
			margin = axisOptions.autoscaleMargin;
			if (margin != 0) {
				max = max.doubleValue() + axis.tickSize * margin;
				if ((max.doubleValue() > 0) && (axis.datamax.doubleValue() <= 0)) {
					max = 0;
				}
				max = axis.tickSize * Math.ceil(max.doubleValue() / axis.tickSize);
			}
		}
		axis.min = min;
		axis.max = max;

	}

	//=========================================================================
	/**
	 * Method calculateSpacing
	 */
	//=========================================================================
	private void calculateSpacing() {

		String max_label = "";
		List<Tick> ticks = yaxis.ticks.getTicks();
		for (int i = 0; i < ticks.size(); i++) {
			if (ticks.get(i).getLabel().length() > max_label.length()) {
				max_label = ticks.get(i).getLabel();
			}
		}
		SpanElement dummyDiv = Document.get().createSpanElement();
		dummyDiv.getStyle().setProperty("position", "absolute");
		dummyDiv.getStyle().setProperty("font-size", "smaller");
		dummyDiv.getStyle().setProperty("class", "glotr-grid-label");
		dummyDiv.setInnerText(max_label);
		div.appendChild(dummyDiv);
		labelMaxWidth = dummyDiv.getOffsetWidth();
		labelMaxHeight = dummyDiv.getOffsetHeight();
		div.removeChild(dummyDiv);

		int maxOutset = 2;
		if (options.points.show) {
			maxOutset = Math.max(maxOutset, options.points.radius + options.points.lineWidth / 2);
		}
		for (int i = 0; i < series.size(); i++) {
			if (series.get(i).points.show) {
				maxOutset = Math.max(maxOutset, series.get(i).points.radius + series.get(i).points.lineWidth / 2);
			}
		}
		plotOffset = new Bounds(maxOutset, maxOutset, maxOutset, maxOutset);
		plotOffset.addX(labelMaxWidth + options.grid.labelMargin);
		plotOffset.addHeight(labelMaxHeight + options.grid.labelMargin);
		plotWidth = canvasWidth - (int)plotOffset.getX() - (int)plotOffset.getWidth();
		plotHeight = canvasHeight - (int)plotOffset.getY() - (int)plotOffset.getHeight();
		hozScale = plotWidth / (xaxis.max.doubleValue() - xaxis.min.doubleValue());
		vertScale = plotHeight / (yaxis.max.doubleValue() - yaxis.min.doubleValue());

	}

	/**
	 *
	 * @param axis
	 * @param axisOptions
	 */
	private void calculateTicks(Axis axis, PlotOptions.AxisOptions axisOptions) {

		axis.ticks = new Ticks();
		if (axisOptions.ticks != null) {
			Ticks ticks = axisOptions.ticks;
			if (ticks.getMin() == null) {
				axis.ticks.setMin(new Tick(axis.min, axisOptions.tickFormatter.format(axis.min)));
			}
			if (ticks.getMax() == null) {
				axis.ticks.setMax(new Tick(axis.max, axisOptions.tickFormatter.format(axis.max)));
			}
			List<Tick> tick = ticks.getTicks();
			for (int i = 0; i < tick.size(); i++) {
				axis.ticks.addTick(new Tick(tick.get(i)));
			}
		}
		else {
			Double start = new Double(axis.tickSize * Math.ceil(axis.min.doubleValue() / axis.tickSize));

			for (int i = 0; start.doubleValue() + i * axis.tickSize <= axis.max.doubleValue(); ++i) {
				Double v = start.doubleValue() + i * axis.tickSize;
				Number decimals = axisOptions.tickDecimals;
				if (decimals == null) {
					decimals = 1 - Math.floor(Math.log10(axis.tickSize));
				}
				if (decimals.intValue() < 0) {
					decimals = 0;
				}
				v = Double.valueOf(toFixed(v.doubleValue(), decimals.intValue()));
				axis.ticks.addTick(new Tick(v, axisOptions.tickFormatter.format(v)));
			}
		}

	}

	//=========================================================================
	/**
	 * Method drawGrid
	 */
	//=========================================================================
	private void drawGrid() {

		canvas.saveContext();
		canvas.translate(plotOffset.getX(), plotOffset.getY());

		canvas.setLineWidth(1);
		canvas.setStrokeStyle(options.grid.tickColor);
		canvas.beginPath();
		List<Tick> ticks = xaxis.ticks.getTicks();
		for (int i = 0; i < ticks.size(); i++) {
			Number v = ticks.get(i).getValue();
			if (v.equals(xaxis.min) || v.equals(xaxis.min)) {
				continue;
			}
			canvas.moveTo(Math.floor(tHoz(v.doubleValue())) + canvas.getLineWidth() / 2, 0);
			canvas.lineTo(Math.floor(tHoz(v.doubleValue())) + canvas.getLineWidth() / 2, plotHeight);
		}
		ticks = yaxis.ticks.getTicks();
		for (int i = 0; i < ticks.size(); i++) {
			Number v = ticks.get(i).getValue();
			if (v.equals(yaxis.min) || v.equals(yaxis.max)) {
				continue;
			}
			canvas.moveTo(0, Math.floor(tVert(v.doubleValue())) + canvas.getLineWidth() / 2);
			canvas.lineTo(plotWidth, Math.floor(tVert(v.doubleValue())) + canvas.getLineWidth() / 2);
		}
		canvas.stroke();

		canvas.setLineWidth(2.0);
		canvas.setStrokeStyle(options.grid.color);
		canvas.setLineJoin("round");
		canvas.strokeRect(0, 0, plotWidth, plotHeight);
		canvas.restoreContext();

	}

	//=========================================================================
	/**
	 * Method drawLabels
	 */
	//=========================================================================
	private void drawLabels() {

		int noLabels = 0;
		List<Tick> ticks = xaxis.ticks.getTicks();
		for (int i = 0; i < ticks.size(); i++) {
			if (ticks.get(i).getLabel() != null) {
				++noLabels;
			}
		}
		double xBoxWidth = plotWidth / noLabels;

		while (ticks.size() != xLabels.size()) {
			if (ticks.size() > xLabels.size()) {
				DivElement d = Document.get().createDivElement();
				d.getStyle().setProperty("position", "absolute");
				d.getStyle().setProperty("font-size", "smaller");
				d.getStyle().setProperty("color", options.grid.color.toString());
				d.getStyle().setProperty("text-align", "center");
				d.setClassName("glotr-grid-label");
				xLabels.add(d);
			}
			else if (xLabels.size() > ticks.size()) {
				Element d = xLabels.remove(0);
				div.removeChild(d);
				d = null;
			}
		}
		for (int i = 0; i < ticks.size(); i++) {
			Tick tick = ticks.get(i);
			DivElement label = xLabels.get(i);
			label.getStyle().setPropertyPx("top", (int)(getAbsoluteTop() + plotOffset.getY() + plotHeight + options.grid.labelMargin));
			label.getStyle().setPropertyPx("left",
					(int)(getAbsoluteLeft() + plotOffset.getX() + tHoz(tick.getValue().doubleValue()) - xBoxWidth / 2));
			label.getStyle().setPropertyPx("width", (int)xBoxWidth);
			label.setInnerText(tick.getLabel());
			div.appendChild(label);
		}
		ticks = yaxis.ticks.getTicks();
		while (ticks.size() != yLabels.size()) {
			if (ticks.size() > yLabels.size()) {
				DivElement d = Document.get().createDivElement();
				d.getStyle().setProperty("position", "absolute");
				d.getStyle().setProperty("font-size", "smaller");
				d.getStyle().setProperty("color", options.grid.color.toString());
				d.getStyle().setProperty("text-align", "center");
				d.setClassName("glotr-grid-label");
				yLabels.add(d);
			}
			else if (yLabels.size() > ticks.size()) {
				Element d = yLabels.remove(0);
				div.removeChild(d);
				d = null;
			}
		}
		for (int i = 0; i < ticks.size(); i++) {
			Tick tick = ticks.get(i);
			DivElement label = yLabels.get(i);
			label.getStyle().setPropertyPx("top",
					(int)(getAbsoluteTop() + plotOffset.getY() + tVert(tick.getValue().doubleValue()) - labelMaxHeight / 2));
			label.getStyle().setPropertyPx("left", getAbsoluteLeft());
			label.getStyle().setPropertyPx("width", labelMaxWidth);
			label.setInnerText(tick.getLabel());
			div.appendChild(label);
		}

	}

	//=========================================================================
	/**
	 * Method drawNow
	 */
	//=========================================================================
	private void drawNow() {
		
		if(!options.pie.show){
			drawGrid();
			drawLabels();
			for (int i = 0; i < series.size(); i++) {
				drawSeries(series.get(i));
			}
		}
		else{
			drawPie();
		}

	}
	//=========================================================================
	/**
	 * Method drawSeries
	 * @param series
	 */
	//=========================================================================
	private void drawPie() {
		Double total = new Double(0.0);
		for(int i=0;i<series.size();i++){
			total+=series.get(i).getXSum();
		}
		if(total>0){
			//draw pie chart.
		}
		else{
			while(xLabels.size()>0){
				Element d = xLabels.remove(0);
				div.removeChild(d);
				d = null;
			}
			DivElement d = Document.get().createDivElement();
			d.getStyle().setProperty("position", "absolute");
			d.getStyle().setProperty("font-size", "smaller");
			d.getStyle().setProperty("color", Color.BLACK.toString());
			d.getStyle().setProperty("text-align", "center");
			d.setClassName("glotr-grid-label");
			xLabels.add(d);
			DivElement label = xLabels.get(0);
			label.getStyle().setPropertyPx("top", (int)(getAbsoluteTop() + plotOffset.getY() + plotHeight/2 + options.grid.labelMargin));
			label.getStyle().setPropertyPx("left",
					(int)(getAbsoluteLeft() + plotOffset.getX() + plotWidth/2));
			label.setInnerText("Invalid Pie Chart Configuration");
			div.appendChild(label);
		}
	}

	//=========================================================================
	/**
	 * Method drawSeries
	 * @param series
	 */
	//=========================================================================
	private void drawSeries(GlotrData series) {

		if (series.lines.show || (!series.bars.show && !series.points.show)) {
			drawSeriesLines(series);
		}
		if (series.bars.show) {
			drawSeriesBars(series);
		}
		if (series.points.show) {
			drawSeriesPoints(series);
		}

	}

	//=========================================================================
	/**
	 * Method drawSeriesBars
	 * @param series
	 */
	//=========================================================================
	private void drawSeriesBars(GlotrData series) {

		canvas.saveContext();
		canvas.translate(plotOffset.getX(), plotOffset.getY());
		canvas.setLineJoin("round");
		double bw = series.bars.barWidth;
		double lw = Math.min(series.bars.lineWidth, bw);

		canvas.setLineWidth(lw);
		canvas.setStrokeStyle(series.color);
		if (series.bars.fill) {
			canvas.setFillStyle((series.bars.fillColor != null) ? series.bars.fillColor : series.color);
		}
		plotBars(series.data, bw, 0, series.bars.fill);
		canvas.restoreContext();

	}

	//=========================================================================
	/**
	 * Method drawSeriesLines
	 * @param series
	 */
	//=========================================================================
	private void drawSeriesLines(GlotrData series) {

		canvas.saveContext();
		canvas.translate(plotOffset.getX(), plotOffset.getY());
		canvas.setLineJoin("round");

		int lineWidth = series.lines.lineWidth;
		int shadowSize = series.shadowSize.intValue();

		if (shadowSize > 0) {
			canvas.setLineWidth(shadowSize / 2);
			canvas.setStrokeStyle(new Color(0, 0, 0, 0.1f));
			plotLine(series.data, lineWidth / 2 + shadowSize / 2 + canvas.getLineWidth() / 2);

			canvas.setLineWidth(shadowSize / 2);
			canvas.setStrokeStyle(new Color(0, 0, 0, 0.2f));
			plotLine(series.data, lineWidth / 2 + canvas.getLineWidth() / 2);
		}

		canvas.setLineWidth(lineWidth);
		canvas.setStrokeStyle(series.color);
		if (series.lines.fill) {
			canvas.setFillStyle(((series.lines.fillColor != null) ? series.lines.fillColor : series.color));
			plotLineArea(series.data, 0);
		}
		plotLine(series.data, 0);
		canvas.restoreContext();

	}

	//=========================================================================
	/**
	 * Method drawSeriesPoints
	 * @param series
	 */
	//=========================================================================
	private void drawSeriesPoints(GlotrData series) {

		canvas.saveContext();
		canvas.translate(plotOffset.getX(), plotOffset.getY());
		//int lw = series.lines.lineWidth;
		double sw = series.shadowSize.doubleValue();
		if (sw > 0) {
			/**
			 * Draw fake shadow in two steps.
			 */
			canvas.setLineWidth(sw / 2);
			canvas.setStrokeStyle(new Color("rgba(0,0,0,0.1)"));
			plotPointShadows(series.data, sw / 2 + canvas.getLineWidth() / 2, series.points.radius);

			canvas.setLineWidth(sw / 2);
			canvas.setStrokeStyle(new Color("rgba(0,0,0,0.2)"));
			plotPointShadows(series.data, canvas.getLineWidth() / 2, series.points.radius);
		}

		canvas.setLineWidth(series.points.lineWidth);
		canvas.setStrokeStyle(series.color);
		canvas.setFillStyle((series.points.fillColor != null) ? series.points.fillColor : series.color);
		plotPoints(series.data, series.points.radius, series.points.fill);
		canvas.restoreContext();

	}

	//=========================================================================
	/**
	 * Method extendXRangeIfNeededByBar
	 */
	//=========================================================================
	private void extendXRangeIfNeededByBar() {

		if (options.xaxis.max == null) {
			Number newmax = xaxis.max;
			for (int i = 0; i < series.size(); i++) {
				if (series.get(i).bars.show && (series.get(i).bars.barWidth + xaxis.datamax.doubleValue() > newmax.doubleValue())) {
					newmax = xaxis.max.doubleValue() + series.get(i).bars.barWidth;
				}
			}
			xaxis.max = newmax;
		}

	}

	//=========================================================================
	/**
	 * Method findDataRanges
	 */
	//=========================================================================
	private void findDataRanges() {

		yaxis.datamin = xaxis.datamin = 0;
		yaxis.datamax = xaxis.datamax = 1;
		if (series.size() == 0) {
			return;
		}
		boolean found = false;
		for (int i = 0; i < series.size(); i++) {
			if (series.get(i).data.size() > 0) {
				xaxis.datamin = xaxis.datamax = series.get(i).data.get(0).x;
				yaxis.datamin = yaxis.datamax = series.get(i).data.get(0).y;
				found = true;
				break;
			}
		}
		if (!found) {
			return;
		}

		for (int i = 0; i < series.size(); i++) {
			ArrayList<Coordinate> data = series.get(i).data;
			for (int j = 0; j < data.size(); j++) {
				double x = data.get(j).x;
				double y = data.get(j).y;
				if (x < xaxis.datamin.doubleValue()) {
					xaxis.datamin = x;
				}
				else if (x > xaxis.datamax.doubleValue()) {
					xaxis.datamax = x;
				}
				if (y < yaxis.datamin.doubleValue()) {
					yaxis.datamin = y;
				}
				else if (y > yaxis.datamax.doubleValue()) {
					yaxis.datamax = y;
				}
			}
		}

	}

	//=========================================================================
	/**
	 * Method insertLegend
	 */
	//=========================================================================
	private void insertLegend() {

		if (!options.legend.show) {
			return;
		}

	}

	//=========================================================================
	/**
	 * Method plotBars
	 * @param data
	 * @param barWidth
	 * @param offset
	 * @param fill
	 */
	//=========================================================================
	private void plotBars(ArrayList<Coordinate> data, double barWidth, double offset, boolean fill) {

		if (data.size() < 2) {
			return;
		}

		for (int i = 0; i < data.size(); i++) {
			double x = data.get(i).x, y = data.get(i).y;
			boolean drawLeft = true, drawTop = true, drawRight = true;
			double left = x, right = x + barWidth, bottom = 0, top = y;

			if ((right < xaxis.min.doubleValue()) || (left > xaxis.max.doubleValue()) || (top < yaxis.min.doubleValue())
					|| (bottom > yaxis.max.doubleValue())) {
				continue;
			}

			if (left < xaxis.min.doubleValue()) {
				left = xaxis.min.doubleValue();
				drawLeft = false;
			}

			if (right > xaxis.max.doubleValue()) {
				right = xaxis.max.doubleValue();
				drawRight = false;
			}

			if (bottom < yaxis.min.doubleValue()) {
				bottom = yaxis.min.doubleValue();
			}

			if (top > yaxis.max.doubleValue()) {
				top = yaxis.max.doubleValue();
				drawTop = false;
			}

			/**
			 * Fill the bar.
			 */
			if (fill) {
				canvas.beginPath();
				canvas.moveTo(tHoz(left), tVert(bottom) + offset);
				canvas.lineTo(tHoz(left), tVert(top) + offset);
				canvas.lineTo(tHoz(right), tVert(top) + offset);
				canvas.lineTo(tHoz(right), tVert(bottom) + offset);
				canvas.fill();
			}

			/**
			 * Draw bar outline/border.
			 */
			if (drawLeft || drawRight || drawTop) {
				canvas.beginPath();
				canvas.moveTo(tHoz(left), tVert(bottom) + offset);
				if (drawLeft) {
					canvas.lineTo(tHoz(left), tVert(top) + offset);
				}
				else {
					canvas.moveTo(tHoz(left), tVert(top) + offset);
				}
				if (drawTop) {
					canvas.lineTo(tHoz(right), tVert(top) + offset);
				}
				else {
					canvas.moveTo(tHoz(right), tVert(top) + offset);
				}
				if (drawRight) {
					canvas.lineTo(tHoz(right), tVert(bottom) + offset);
				}
				else {
					canvas.moveTo(tHoz(right), tVert(bottom) + offset);
				}
				canvas.stroke();
			}
		}

	}

	/**
	 *
	 * @param data
	 * @param offset
	 */
	private void plotLine(ArrayList<Coordinate> data, double offset) {

		if (data.size() < 2) {
			return;
		}
		double prevx = tHoz(data.get(0).x);
		double prevy = tVert(data.get(0).y);
		canvas.beginPath();
		canvas.moveTo(prevx, prevy);

		for (int i = 0; i < data.size() - 1; i++) {
			double x1 = data.get(i).x;
			double y1 = data.get(i).y;
			double x2 = data.get(i + 1).x;
			double y2 = data.get(i + 1).y;

			if ((y1 <= y2) && (y1 < yaxis.min.doubleValue())) {
				if (y2 < yaxis.min.doubleValue()) {
					continue;
				}
				x1 = (yaxis.min.doubleValue() - y1) / (y2 - y1) * (x2 - x1) + x1;
				y1 = yaxis.min.doubleValue();
			}
			else if ((y2 <= y1) && (y2 < yaxis.min.doubleValue())) {
				if (y1 < yaxis.min.doubleValue()) {
					continue;
				}
				x2 = (yaxis.min.doubleValue() - y1) / (y2 - y1) * (x2 - x1) + x1;
				y2 = yaxis.min.doubleValue();
			}
			if ((y1 >= y2) && (y1 > yaxis.max.doubleValue())) {
				if (y2 > yaxis.max.doubleValue()) {
					continue;
				}
				x1 = (yaxis.max.doubleValue() - y1) / (y2 - y1) * (x2 - x1) + x1;
				y1 = yaxis.max.doubleValue();
			}
			else if ((y2 >= y1) && (y2 > yaxis.max.doubleValue())) {
				if (y1 > yaxis.max.doubleValue()) {
					continue;
				}
				x2 = (yaxis.max.doubleValue() - y1) / (y2 - y1) * (x2 - x1) + x1;
				y2 = yaxis.max.doubleValue();
			}
			if ((x1 <= x2) && (x1 < xaxis.min.doubleValue())) {
				if (x2 < xaxis.min.doubleValue()) {
					continue;
				}
				y1 = (xaxis.min.doubleValue() - x1) / (x2 - x1) * (y2 - y1) + y1;
				x2 = xaxis.min.doubleValue();
			}
			else if ((x2 <= x1) && (x2 < xaxis.min.doubleValue())) {
				if (x1 < xaxis.min.doubleValue()) {
					continue;
				}
				y2 = (xaxis.min.doubleValue() - x1) / (x2 - x1) * (y2 - y1) + y1;
				x2 = xaxis.min.doubleValue();
			}
			if ((x1 >= x2) && (x1 > xaxis.max.doubleValue())) {
				if (x2 > xaxis.max.doubleValue()) {
					continue;
				}
				y1 = (xaxis.max.doubleValue() - x1) / (x2 - x1) * (y2 - y1) + y1;
				x1 = xaxis.max.doubleValue();
			}
			else if ((x2 >= x1) && (x2 > xaxis.max.doubleValue())) {
				if (x1 > xaxis.max.doubleValue()) {
					continue;
				}
				y2 = (xaxis.max.doubleValue() - x1) / (x2 - x1) * (y2 - y1) + y1;
				x2 = xaxis.max.doubleValue();
			}
			if ((prevx != tHoz(x1)) || (prevy != tVert(y1) + offset)) {
				canvas.moveTo(tHoz(x1), tVert(y1) + offset);
			}

			prevx = tHoz(x2);
			prevy = tVert(y2) + offset;
			canvas.lineTo(prevx, prevy);
		}
		canvas.stroke();

	}

	//=========================================================================
	/**
	 * Method plotLineArea
	 * @param data
	 * @param offset
	 */
	//=========================================================================
	private void plotLineArea(ArrayList<Coordinate> data, double offset) {

		if (data.size() < 2) {
			return;
		}

		double bottom = Math.min(Math.max(0, yaxis.min.doubleValue()), yaxis.max.doubleValue());
		double top, lastX = 0;
		boolean first = true;

		canvas.beginPath();
		for (int i = 0; i < data.size() - 1; ++i) {

			double x1 = data.get(i).x, y1 = data.get(i).y, x2 = data.get(i + 1).x, y2 = data.get(i + 1).y;

			if ((x1 <= x2) && (x1 < xaxis.min.doubleValue())) {
				if (x2 < xaxis.min.doubleValue()) {
					continue;
				}
				y1 = (xaxis.min.doubleValue() - x1) / (x2 - x1) * (y2 - y1) + y1;
				x1 = xaxis.min.doubleValue();
			}
			else if ((x2 <= x1) && (x2 < xaxis.min.doubleValue())) {
				if (x1 < xaxis.min.doubleValue()) {
					continue;
				}
				y2 = (xaxis.min.doubleValue() - x1) / (x2 - x1) * (y2 - y1) + y1;
				x2 = xaxis.min.doubleValue();
			}

			if ((x1 >= x2) && (x1 > xaxis.max.doubleValue())) {
				if (x2 > xaxis.max.doubleValue()) {
					continue;
				}
				y1 = (xaxis.max.doubleValue() - x1) / (x2 - x1) * (y2 - y1) + y1;
				x1 = xaxis.max.doubleValue();
			}
			else if ((x2 >= x1) && (x2 > xaxis.max.doubleValue())) {
				if (x1 > xaxis.max.doubleValue()) {
					continue;
				}
				y2 = (xaxis.max.doubleValue() - x1) / (x2 - x1) * (y2 - y1) + y1;
				x2 = xaxis.max.doubleValue();
			}

			if (first) {
				canvas.moveTo(tHoz(x1), tVert(bottom));
				first = false;
			}

			/**
			 * Now check the case where both is outside.
			 */
			if ((y1 >= yaxis.max.doubleValue()) && (y2 >= yaxis.max.doubleValue())) {
				canvas.lineTo(tHoz(x1), tVert(yaxis.max.doubleValue()));
				canvas.lineTo(tHoz(x2), tVert(yaxis.max.doubleValue()));
				continue;
			}
			else if ((y1 <= yaxis.min.doubleValue()) && (y2 <= yaxis.min.doubleValue())) {
				canvas.lineTo(tHoz(x1), tVert(yaxis.min.doubleValue()));
				canvas.lineTo(tHoz(x2), tVert(yaxis.min.doubleValue()));
				continue;
			}

			/**
			 * Else it's a bit more complicated, there might
			 * be two rectangles and two triangles we need to fill
			 * in; to find these keep track of the current x values.
			 */
			double x1old = x1, x2old = x2;

			/**
			 * And clip the y values, without shortcutting.
			 * Clip with ymin.
			 */
			if ((y1 <= y2) && (y1 < yaxis.min.doubleValue()) && (y2 >= yaxis.min.doubleValue())) {
				x1 = (yaxis.min.doubleValue() - y1) / (y2 - y1) * (x2 - x1) + x1;
				y1 = yaxis.min.doubleValue();
			}
			else if ((y2 <= y1) && (y2 < yaxis.min.doubleValue()) && (y1 >= yaxis.min.doubleValue())) {
				x2 = (yaxis.min.doubleValue() - y1) / (y2 - y1) * (x2 - x1) + x1;
				y2 = yaxis.min.doubleValue();
			}

			/**
			 * Clip with ymax.
			 */
			if ((y1 >= y2) && (y1 > yaxis.max.doubleValue()) && (y2 <= yaxis.max.doubleValue())) {
				x1 = (yaxis.max.doubleValue() - y1) / (y2 - y1) * (x2 - x1) + x1;
				y1 = yaxis.max.doubleValue();
			}
			else if ((y2 >= y1) && (y2 > yaxis.max.doubleValue()) && (y1 <= yaxis.max.doubleValue())) {
				x2 = (yaxis.max.doubleValue() - y1) / (y2 - y1) * (x2 - x1) + x1;
				y2 = yaxis.max.doubleValue();
			}

			/**
			 * If the x value was changed we got a rectangle to fill.
			 */
			if (x1 != x1old) {
				top = (y1 <= yaxis.min.doubleValue()) ? top = yaxis.min.doubleValue() : yaxis.max.doubleValue();
				canvas.lineTo(tHoz(x1old), tVert(top));
				canvas.lineTo(tHoz(x1), tVert(top));
			}

			/**
			 * Fill the triangles.
			 */
			canvas.lineTo(tHoz(x1), tVert(y1));
			canvas.lineTo(tHoz(x2), tVert(y2));

			/**
			 * Fill the other rectangle if it's there.
			 */
			if (x2 != x2old) {
				top = (y2 <= yaxis.min.doubleValue()) ? yaxis.min.doubleValue() : yaxis.max.doubleValue();
				canvas.lineTo(tHoz(x2old), tVert(top));
				canvas.lineTo(tHoz(x2), tVert(top));
			}

			lastX = Math.max(x2, x2old);
		}
		/*
		 * canvas.beginPath();
		 * canvas.moveTo(tHoz(data.get(0).x), tVert(0));
		 * for (var i = 0; i < data.length; i++) {
		 *   canvas.lineTo(tHoz(data.get(i).x), tVert(data.get(i).y));
		 * }
		 * canvas.lineTo(tHoz(data.get(data.size - 1).x), tVert(0));
		 */
		canvas.lineTo(tHoz(lastX), tVert(bottom));
		canvas.closePath();
		canvas.fill();

	}

	//=========================================================================
	/**
	 * Method plotPoints
	 * @param data
	 * @param radius
	 * @param fill
	 */
	//=========================================================================
	private void plotPoints(ArrayList<Coordinate> data, int radius, boolean fill) {

		for (int i = 0; i < data.size(); ++i) {
			double x = data.get(i).x, y = data.get(i).y;
			if ((x < xaxis.min.doubleValue()) || (x > xaxis.max.doubleValue()) || (y < yaxis.min.doubleValue()) || (y > yaxis.max.doubleValue())) {
				continue;
			}

			canvas.beginPath();
			canvas.arc(tHoz(x), tVert(y), radius, 0, 2 * Math.PI, true);
			if (fill) {
				canvas.fill();
			}
			canvas.stroke();
		}

	}

	//=========================================================================
	/**
	 * Method plotPointShadows
	 * @param data
	 * @param offset
	 * @param radius
	 */
	//=========================================================================
	private void plotPointShadows(ArrayList<Coordinate> data, double offset, int radius) {

		for (int i = 0; i < data.size(); ++i) {
			double x = data.get(i).x, y = data.get(i).y;
			if ((x < xaxis.min.doubleValue()) || (x > xaxis.max.doubleValue()) || (y < yaxis.min.doubleValue()) || (y > yaxis.max.doubleValue())) {
				continue;
			}
			canvas.beginPath();
			canvas.arc(tHoz(x), tVert(y) + offset, radius, 0, Math.PI, false);
			canvas.stroke();
		}

	}

	//=========================================================================
	/**
	 * Method tHoz
	 * @param x
	 * @return double
	 */
	//=========================================================================
	private double tHoz(double x) {
		return (x - xaxis.min.doubleValue()) * hozScale;
	}

	/**
	 *
	 * @param value
	 * @param decimals
	 * @return
	 */
	private native String toFixed(double value, int decimals)    /*-{
			return value.toFixed(decimals);
		}-*/
	;

	//=========================================================================
	/**
	 * Method tVert
	 * @param y
	 * @return double
	 */
	//=========================================================================
	private double tVert(double y) {
		return plotHeight - (y - yaxis.min.doubleValue()) * vertScale;
	}
}

